/** 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.free.i.virtual.machine.hal.devicealias.laboratory;

import it.free.i.virtual.machine.hal.devicealias.DeviceAliasCommandExecutionException;
import it.free.i.virtual.machine.hal.devicealias.NotificationFacilities;
import it.free.i.virtual.machine.hal.devicealias.command.DeviceAliasPortCommand;
import it.free.i.virtual.machine.hal.devicealias.command.DeviceAliasPortCommandImpl;
import it.free.i.virtual.machine.hal.devicealias.command.DeviceAliasPortCommandVerb;
import it.free.i.virtual.machine.hal.devicealias.controller.DeviceAliasPortController;
import it.free.i.virtual.machine.knowledgebase.ciuc.basket.NotificationsListsBasket;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.management.Notification;

import org.apache.log4j.Logger;

/**  
 * Invoke all ports reading at a specified interval to fetch snapshots of the system. This should run for
 * example every 1000 milliseconds. Acts like a clock.
 * This was the doc mud impersonator (docmudimpersonator)
 * @author stefano
 */
public class DeviceAliasPortReadClockDaemon implements Runnable, Observer {

	private Logger logger = Logger.getLogger(DeviceAliasPortReadClockDaemon.class);
	
	private boolean continueLooping = false;
	private double notificationCounter = 0D; //To count locally received noifications
	private long readInterval = 0L;
	//Store here the notifications collected per unit of time
	private List<Notification> oneClockCycleList = new ArrayList<Notification>();

	private static DeviceAliasPortReadClockDaemon CIUC_COLLECTING_CLOCK_INSTANCE = null;
	
	/**
	 * Private, don't use
	 */
	private DeviceAliasPortReadClockDaemon(){}
	
	/**
	 * Singleton instance accessor
	 * @return the single instance of the doc mud impersonator
	 */
	public static final DeviceAliasPortReadClockDaemon getInstance(){
		
		if(CIUC_COLLECTING_CLOCK_INSTANCE == null){
		   synchronized(DeviceAliasPortReadClockDaemon.class){
			   if(CIUC_COLLECTING_CLOCK_INSTANCE == null){
				   CIUC_COLLECTING_CLOCK_INSTANCE = new DeviceAliasPortReadClockDaemon();
			   }
		   }
		}
		return CIUC_COLLECTING_CLOCK_INSTANCE;
	}
	
	/**
	 * This is what a subsystem providing intelligence can do with the controller of the abstracted
	 * hardware resources
	 * This simulation entry point does not imitate the device alias generating data for the 
	 * high level logic
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		
		logger.info("Enabling daemon firing invocations to device aliases' ports");
		
		continueLooping = true;
			
		/** 
		 * This will cause the dummy devices in the laboratory package
		 * to send back an answer to the message listener, that's the echo to read ports
		 */
		while(continueLooping){
			
			try{
				Thread.sleep(readInterval);
				createNewComposableInfoUnitsChain();
				//logger.info("Firing readable devices wake up");
				doInvokeReadable();
				//logger.info("Capturing system I/O snapshot");
				
			}catch(InterruptedException iExc) {
				   iExc.printStackTrace();
			}catch(Exception exc) {
				   exc.printStackTrace();
			}		
		}
	}
	
	/**
	 * Acquire the lock until the swap list for one clock cycle has been cleaned up
	 */
	private void createNewComposableInfoUnitsChain() throws Exception{
		
		synchronized(this){
			
			//Add this to the knowledge base basket: a list of values received by sensors are put together per
			// unit of time, this is a ciuc (compose-able information units chain), then put all these lists of ciuc in one place, that is the swapping basket
			//this.oneClockCycleList will have inside the information received (solicited and unsolicited) in the unit of clock time 
			if( (this.oneClockCycleList != null && (!this.oneClockCycleList.isEmpty()) )){
			   List<Notification> swapList = new ArrayList<Notification>();
			   
			   for(Notification notification: this.oneClockCycleList){
				   swapList.add(notification);   
			   }
			   
			   NotificationsListsBasket.getInstance().add(String.valueOf(System.currentTimeMillis()), swapList);
			}
			this.oneClockCycleList = new ArrayList<Notification>();
		}
	}
	
	private static void doInvokeReadable() throws DeviceAliasCommandExecutionException {
		
		DeviceAliasPortCommand deviceAliasPortCommandRead = new DeviceAliasPortCommandImpl(); 
		deviceAliasPortCommandRead.setVerb(DeviceAliasPortCommandVerb.PORT_DO_READ);
		
		DeviceAliasPortController.getInstance().fireCommand(deviceAliasPortCommandRead);
		
	}
	
//	protected void doDriveAccelerate() throws DeviceAliasCommandExecutionException{
//		
//		DeviceAliasPortCommand deviceAliasPortCommandAccelerate = new DeviceAliasPortCommandImpl();    
//		deviceAliasPortCommandAccelerate.setVerb(DeviceAliasPortCommandVerb.PORT_DRIVE_ACCELERATE);
//			
//		DeviceAliasPortController.getInstance().fireCommand(deviceAliasPortCommandAccelerate);
//	}
//	
//	protected void doDriveBrake() throws DeviceAliasCommandExecutionException{
//		
//		DeviceAliasPortCommand deviceAliasPortCommandBrake = new DeviceAliasPortCommandImpl();    
//		deviceAliasPortCommandBrake.setVerb(DeviceAliasPortCommandVerb.PORT_DRIVE_BRAKE);
//			
//		DeviceAliasPortController.getInstance().fireCommand(deviceAliasPortCommandBrake);
//		
//	}
//	
//	protected void doDriveLeft() throws DeviceAliasCommandExecutionException{
//		
//		DeviceAliasPortCommand deviceAliasPortCommandLeft = new DeviceAliasPortCommandImpl();    
//		deviceAliasPortCommandLeft.setVerb(DeviceAliasPortCommandVerb.PORT_DRIVE_LEFT);
//			
//		DeviceAliasPortController.getInstance().fireCommand(deviceAliasPortCommandLeft);
//	}
//	
//	protected void doDriveRight() throws DeviceAliasCommandExecutionException{
//		
//		DeviceAliasPortCommand deviceAliasPortCommandRight = new DeviceAliasPortCommandImpl();    
//		deviceAliasPortCommandRight.setVerb(DeviceAliasPortCommandVerb.PORT_DRIVE_RIGHT);
//			
//		DeviceAliasPortController.getInstance().fireCommand(deviceAliasPortCommandRight);
//	}
	
	/**
	 * Stop invoking the ports to have back readings
	 */
	public final void stopInvokingDeviceAliasPorts(){
		continueLooping = false;
	}

	/**
	 * Receive notifications about values read
	 */
	public void update(Observable o, Object notification) {
		
		notificationCounter++;
		
		Notification aNotification = (Notification) notification;
		
		if(this.oneClockCycleList != null){
		   this.oneClockCycleList.add(aNotification);	
		}else{
			  logger.info("Lost information packet: " + aNotification);
		}

		logger.info(NotificationFacilities.notificationToString(aNotification, notificationCounter));
	}

	/**
	 * @param readInterval the readInterval to set
	 */
	public void setReadInterval(long readInterval) {
		this.readInterval = readInterval;
	}
}
